package emergence.game;

import emergence.entities.Mob;
import emergence.entities.Player;
import emergence.entities.Mob.MobSpriteSet;
import emergence.entities.MobClass.ChampionClass;
import emergence.entities.MobClass.MobClass;
import emergence.entities.attribute.MobStatSet;
import emergence.entities.attribute.StatAttribute;
import emergence.entities.npc.WanderingNPC;
import emergence.game.gfx.ColoredSprite;
import emergence.game.gfx.Colors;
import emergence.game.gfx.Font;
import emergence.game.gfx.Screen;
import emergence.game.gfx.Screen.ScreenSelectFilter;
import emergence.game.gfx.SpriteSheet;
import emergence.game.hud.Hotbar;
import emergence.game.hud.Sidebar;
import emergence.game.level.Level;
import emergence.game.level.pathfinding.Path;
import emergence.game.level.pathfinding.Pathfinder;
import emergence.resource.Resources;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.Border;
import util.Logger;

public class Game implements Runnable {

    public static final int WIDTH = 600;
    public static final int HEIGHT = WIDTH / 16 * 9;
    public static final int SCALE = 2;

    public static final String VERSION = "0.1.0";
    public static final String STATE = "ALPHA";
    public static final String WINDOW_TITLE = "Goblin Menace Version " + VERSION + " " + STATE;

    private final Logger logger = Logger.getDefaultLogger();

    private JFrame frame;
    private Sidebar sidebar;
    private Hotbar hotbar;
    private JPanel screenHolder;
    private JPanel hotbarHolder;
    private JPanel leftSide;
    
    private GameMouseListener mouseList = new GameMouseListener();

    private boolean running = false;

    public static int[] colors = new int[216]; // 216 = 6 * 6 * 6

    private Screen screen;

    public InputHandler input;
    public Level level;

    private Player activePlayer;

    public static SpriteSheet sheet;

    public static int lastFrames;
    private SELECTION_TYPE lastSelectionType;

    public static final Dimension DEFAULT_SIZE = new Dimension(500, 350);

    public static final List<Tickable> tickables = new ArrayList<>();

    public Game() {

    }

    public void init() {
        int index = 0;
        for (int r = 0; r < 6; r++) {
            for (int g = 0; g < 6; g++) {
                for (int b = 0; b < 6; b++) {
                    int rr = (r * 255 / 5);
                    int gg = (g * 255 / 5);
                    int bb = (b * 255 / 5);

                    colors[index++] = rr << 16 | gg << 8 | bb;
                }
            }
        }

        sheet = Resources.getSpriteSheet("BASIC_SHEET");

        input = new InputHandler();

        level = Resources.getLevel("TEST_LEVEL");
        initPlayer();

        screen = new Screen(WIDTH, HEIGHT, SCALE, colors, activePlayer, level);

        level.addEntity(activePlayer, new Point(0, 0));
        level.addEntity(new WanderingNPC(level, Resources.getSpriteSet("MAGUS_SPRITE_SET")), new Point(5, 5));
        
        Font.initFont();

        GridBagConstraints c = new GridBagConstraints();
        frame = new JFrame(WINDOW_TITLE);

        frame.setLayout(new GridBagLayout());
        Border raisedBevel = BorderFactory.createRaisedBevelBorder();
        Border loweredBevel = BorderFactory.createLoweredBevelBorder();
        Border border = BorderFactory.createCompoundBorder(raisedBevel, loweredBevel);

        screenHolder = new JPanel(new GridLayout(1, 1));
        screenHolder.setBorder(border);
        screenHolder.add(screen);
        leftSide = new JPanel();
        
        leftSide.setLayout(new GridBagLayout());
        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 1.0;
        c.weighty = 0.98;
        c.fill = GridBagConstraints.BOTH;
        leftSide.add(screenHolder, c);

        hotbar = new Hotbar(this);
        hotbarHolder = new JPanel(new BorderLayout());
        hotbarHolder.setBorder(border);
        hotbarHolder.add(hotbar);
        c.gridx = 0;
        c.gridy = 1;
        c.weightx = 1.0;
        c.weighty = 0.02;
        c.fill = GridBagConstraints.BOTH;
        leftSide.add(hotbarHolder, c);

        sidebar = new Sidebar(activePlayer);
        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 0.9;
        c.weighty = 1.0;
        c.fill = GridBagConstraints.BOTH;
        frame.add(leftSide, c);
        c.gridx = 1;
        c.gridy = 0;
        c.weightx = 0.1;
        c.weighty = 1.0;
        c.fill = GridBagConstraints.BOTH;
        frame.add(sidebar, c);

        frame.pack();
        frame.setMinimumSize(DEFAULT_SIZE);
        frame.setExtendedState(JFrame.MAXIMIZED_BOTH);

        frame.setLocationRelativeTo(null);

        input.attach(this);
        frame.addKeyListener(input);
        frame.addFocusListener(new GameCompListener(this));
        
        frame.addMouseListener(mouseList);
        screen.addMouseListener(mouseList);

        frame.setVisible(true);

        frame.setFocusable(true);
        frame.requestFocus();
    }

    public void resetInputs() {
        input.resetInputs();
    }

    public void initPlayer() {
        MobSpriteSet defaultSet = new MobSpriteSet();

        defaultSet.setFrontFaceSprite(new ColoredSprite(sheet.getSprite(8, 28, 2, 2), Colors.get(-1, 111, 145, 543)));
        defaultSet.setLeftFaceSprite((new ColoredSprite(sheet.getSprite(4, 28, 2, 2), Colors.get(-1, 111, 145, 543))).flip(false, true));
        defaultSet.setBackFaceSprite(new ColoredSprite(sheet.getSprite(10, 28, 2, 2), Colors.get(-1, 111, 145, 543)));
        defaultSet.setRightFaceSprite(new ColoredSprite(sheet.getSprite(4, 28, 2, 2), Colors.get(-1, 111, 145, 543)));

        defaultSet.setFrontWalkSprite1(new ColoredSprite(sheet.getSprite(0, 28, 2, 2), Colors.get(-1, 111, 145, 543)));
        defaultSet.setFrontWalkSprite2(new ColoredSprite(sheet.getSprite(0, 28, 2, 2), Colors.get(-1, 111, 145, 543)).flip(false, true));

        defaultSet.setBackWalkSprite1(new ColoredSprite(sheet.getSprite(2, 28, 2, 2), Colors.get(-1, 111, 145, 543)));
        defaultSet.setBackWalkSprite2(new ColoredSprite(sheet.getSprite(2, 28, 2, 2), Colors.get(-1, 111, 145, 543)).flip(false, true));

        defaultSet.setRightWalkSprite(new ColoredSprite(sheet.getSprite(6, 28, 2, 2), Colors.get(-1, 111, 145, 543)));
        defaultSet.setLeftWalkSprite(new ColoredSprite(sheet.getSprite(6, 28, 2, 2), Colors.get(-1, 111, 145, 543)).flip(false, true));

        MobSpriteSet championSet = Resources.getSpriteSet("CHAMPION_SPRITE_SET");
        MobSpriteSet magusSet = Resources.getSpriteSet("MAGUS_SPRITE_SET");

        MobSpriteSet prowlerSet = new MobSpriteSet();

        prowlerSet.setFrontFaceSprite(new ColoredSprite(sheet.getSprite(16, 22, 2, 2), Colors.get(-1, 111, 222, 543)));
        prowlerSet.setLeftFaceSprite((new ColoredSprite(sheet.getSprite(12, 22, 2, 2), Colors.get(-1, 111, 222, 543))));
        prowlerSet.setBackFaceSprite(new ColoredSprite(sheet.getSprite(18, 22, 2, 2), Colors.get(-1, 111, 222, 543)));
        prowlerSet.setRightFaceSprite(new ColoredSprite(sheet.getSprite(8, 22, 2, 2), Colors.get(-1, 111, 222, 543)));

        prowlerSet.setFrontWalkSprite1(new ColoredSprite(sheet.getSprite(0, 22, 2, 2), Colors.get(-1, 111, 222, 543)));
        prowlerSet.setFrontWalkSprite2(new ColoredSprite(sheet.getSprite(2, 22, 2, 2), Colors.get(-1, 111, 222, 543)));

        prowlerSet.setBackWalkSprite1(new ColoredSprite(sheet.getSprite(4, 22, 2, 2), Colors.get(-1, 111, 222, 543)));
        prowlerSet.setBackWalkSprite2(new ColoredSprite(sheet.getSprite(6, 22, 2, 2), Colors.get(-1, 111, 222, 543)));

        prowlerSet.setRightWalkSprite(new ColoredSprite(sheet.getSprite(10, 22, 2, 2), Colors.get(-1, 111, 222, 543)));
        prowlerSet.setLeftWalkSprite(new ColoredSprite(sheet.getSprite(14, 22, 2, 2), Colors.get(-1, 111, 222, 543)));

        MobStatSet playerStats = new MobStatSet();
        playerStats.setStrength(new StatAttribute(14));
        playerStats.setVigor(new StatAttribute(16));
        playerStats.setAgility(new StatAttribute(12));
        playerStats.setCharisma(new StatAttribute(12));
        playerStats.setIntelligence(new StatAttribute(14));
        playerStats.setMetamagic(new StatAttribute(12));
        playerStats.setWillpower(new StatAttribute(14));

        MobClass championClass = new ChampionClass(2);

        activePlayer = new Player(level, input, championClass, playerStats, this);
    }

    public synchronized void start() {
        running = true;
        new Thread(this).start();
    }

    public synchronized void stop() {
        running = false;
    }

    @Override
    public void run() {
        init();

        long lastTime = System.nanoTime();
        double nsPerTick = 1E9 / 60.0;

//      int ticks = 0;
        int frames = 0;

        long lastTimer = System.currentTimeMillis();
        double delta = 0.0;

        while (running) {
            long now = System.nanoTime();
            delta += (now - lastTime) / nsPerTick;
            lastTime = now;
            boolean shouldRender = false;

            while (delta >= 1) {
//		ticks++;
                tick();
                delta -= 1.0;
                shouldRender = true;
            }

            if (shouldRender) {
                frames++;
                render();
            }

            if (System.currentTimeMillis() - lastTimer >= 1E3) {
                lastTimer += 1000;
//		System.out.println(ticks + " UPS, " + frames + " FPS");
                lastFrames = frames;
                frames = 0;
//		ticks = 0;
            }

            // Saves CPU cycles
            now = System.nanoTime();
            delta += (now - lastTime) / nsPerTick;
            lastTime = now;
            if (delta < 0.95) {
                try {
                    Thread.yield();
                    Thread.sleep(Math.round(Math.floor(1E3 / 60 * (1 - delta) * 0.90)));
                }
                catch (InterruptedException e) {
                    logger.log(e);
                }
            }
            else if (delta < 1.0) {
                try {
                    Thread.yield();
                    Thread.sleep(1);
                }
                catch (InterruptedException e) {
                    logger.log(e);
                }
            }
            // Check to see if we should continue running
            running = frame.isVisible();
        }
    }

    public boolean isRunning() {
        return running;
    }

    public synchronized void tick() {
        level.tick();

        boolean complete = false;
        while (!complete) {
            try {
                for (Tickable tickable : tickables) {
                    tickable.tick();
                }
                complete = true;
            }
            catch (ConcurrentModificationException ex) {
                logger.log(ex);
            }
        }
    }

    public static void addTickable(Tickable tickable) {
        if (!tickables.contains(tickable)) {
            tickables.add(tickable);
        }
    }

    public static void removeTickable(Tickable tickable) {
        if (tickables.contains(tickable)) {
            tickables.remove(tickable);
        }
    }

    public void render() {
        screen.render(level);
        hotbar.render();
    }

    public Player getActivePlayer() {
        return activePlayer;
    }

    public void requestFrameFocus() {
        frame.requestFocus();
    }
    
    public void startSelection(ScreenSelectFilter filter) {
        screen.startSelect(filter);
    }
    
    public boolean hasSelection() {
        return screen.hasSelection();
    }
    
    public Mob getSelection() {
        return (Mob)screen.getSelection();
    }
    
    public SELECTION_TYPE lastSelectionType() {
        return lastSelectionType;
    }
    
    public void setLastSelectionType(SELECTION_TYPE type) {
        this.lastSelectionType = type;
    }
    
    public void addKeyListener(KeyListener listener) {
        frame.addKeyListener(listener);
    }

    public class GameCompListener implements FocusListener {

        private Game game;

        public GameCompListener(Game game) {
            this.game = game;
        }

        @Override
        public void focusGained(FocusEvent arg0) {
            
        }

        @Override
        public void focusLost(FocusEvent arg0) {
            game.resetInputs();
        }
    }

    public class GameMouseListener extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            requestFrameFocus();
        }
    }

    public enum SELECTION_TYPE {
        ATTACK
    }
    
}
